﻿using System;
using System.IO;
using System.Linq;
using Cubicle.Core.Shared;
using Cubicle.Excel.AddIn;
using Cubicle.Excel.Binding;
using Cubicle.Excel.Config;
using Cubicle.Excel.Hosting;
using Cubicle.Excel.Shared;
using ExcelDna.Integration;

namespace Cubicle.Excel.Functions
{
    public static class BindingFunctions
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        public static void BindScript(string scriptFile, EngineProxy engine)
        {
            try
            {
                var script = Helpers.ReadXml<BindingScript>(scriptFile);
                BindScript(script, engine);
            }
            catch (Exception e)
            {
                _logger.ErrorException("Error during function binding", e);
            }
        }

        public static void BindScript(BindingScript script, EngineProxy engine)
        {
            try
            {
                _logger.Info("Binding commands.");
                if (script.CommandSource != null)
                    foreach (var command in script.CommandSource)
                    {
                        command.Engine = engine.Alias;
                        CubicleAddIn.Binder.Process(command);
                    }

                _logger.Info("Binding functions.");
                if (script.FunctionSource != null)
                    foreach (var function in script.FunctionSource)
                    {
                        function.Engine = engine.Alias;
                        CubicleAddIn.Binder.Process(function);
                    }
            }
            catch (Exception e)
            {
                _logger.ErrorException("Error during function binding", e);
            }
        }

        /// <summary>
        /// Executes binding operations in an Exoengine script file.
        /// </summary>
        /// <param name="scriptFile">The path to the script file</param>
        /// <param name="engineAlias">An Exoengine alias</param>
        /// <returns>True/False</returns>
        [ExcelFunction(
            Name = "cBindScript",
            Category = "Cubicle (Binding)",
            Description = "Bind functions from an .cube script",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static bool BindScript(string scriptFile, string engineAlias = null)
        {
            if (!File.Exists(scriptFile)) return false;
            var proxyEngine = CubicleAddIn.Engines.Get(engineAlias);
            var file = Helpers.ReadBytesFromFile(scriptFile);
            proxyEngine.RunScript(file);
            ExcelAsyncUtil.QueueAsMacro(() => BindScript(scriptFile, proxyEngine));
            return true;
        }

        /// <summary>
        /// Provides information on all bound functions
        /// </summary>
        /// <param name="withHeadings">(Optional) Set to true to return heading information</param>
        /// <returns>Information on all bound functions</returns>
        [ExcelFunction(
           Name = "cListBoundMethods",
            Category = "Cubicle (Binding)",
           Description = "Get a list of all the bound functions and commands",
           IsExceptionSafe = false,
           IsHidden = false,
           IsMacroType = true,
           IsThreadSafe = false,
           IsVolatile = false)]
        public static object[,] ListBindings(bool withHeadings)
        {
            return BindManager.Bindings.Values.Select(x => new
            {
                x.Id,
                x.BindingDescription.FunctionName,
                x.InvokeCount,
                TargetAssembly = x.Engine is LocalProxy ? x.Method.DeclaringType.Assembly.FullName : "n/a (remote)",
                TargetType = x.Engine is LocalProxy ? x.Method.DeclaringType.FullName : "n/a (remote)",
                TargetMethod = x.Engine is LocalProxy ? x.Method.Name : "n/a (remote)",
                TargetOfCall = x.BindingDescription.Target ?? "",
                x.BindingDescription.MethodOffset,
                ReturnType = x.ReturnType.ToString(),
                x.BindingDescription.IsRemote, 
                x.BindingDescription.IsStatic, 
                x.BindingDescription.ResolveParameters, 
                x.BindingDescription.BindToHandle,
                x.BindingDescription.Async, 
                ExcelType = x.BindingDescription is FunctionDescription ? "Function" : "Command",
                Category = x.BindingDescription is FunctionDescription
                    ? ((FunctionDescription)x.BindingDescription).Category
                    : "n/a",
                IsHidden = x.BindingDescription is FunctionDescription
                    ? ((FunctionDescription)x.BindingDescription).IsHidden.ToString()
                    : "n/a",
                IsMacroType = x.BindingDescription is FunctionDescription
                    ? ((FunctionDescription)x.BindingDescription).IsMacroType.ToString()
                    : "n/a",
                IsThreadSafe = x.BindingDescription is FunctionDescription
                    ? ((FunctionDescription)x.BindingDescription).IsThreadSafe.ToString()
                    : "n/a",
                IsVolatile = x.BindingDescription is FunctionDescription
                    ? ((FunctionDescription)x.BindingDescription).IsVolatile.ToString()
                    : "n/a",
                MenuName = x.BindingDescription is CommandDescription
                    ? ((CommandDescription)x.BindingDescription).MenuName
                    : "n/a",
                MenuText = x.BindingDescription is CommandDescription
                    ? ((CommandDescription)x.BindingDescription).MenuText
                    : "n/a"
            }).ToRange(withHeadings, x => x);
        }

    }
}
